package com.hyhy.hycloud.uaa.client;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.hyhy.hycloud.commons.constant.HttpHeader;
import com.hyhy.hycloud.commons.dto.InvokeResult;
import com.hyhy.hycloud.commons.util.mapper.BeanMapper;
import com.hyhy.hycloud.commons.util.reflect.ClassUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.WebRequest;

import java.io.IOException;
import java.util.Map;

/**
 * 用户信息资源服务
 *
 * @author Xuegui Yuan
 * @date 2018-09-25
 */
@AllArgsConstructor
@Slf4j
public class UserInfoResourceService {

    private RestTemplate restTemplate;

    private String uaaUserInfoUrl;

    private ObjectMapper objectMapper;

    private static final String OK_STATUS = "0";

    private static final String USER_TYPE_KEY = "userType";

    private static final String DETAILS_KEY = "details";

    private static final String TOKEN_PARAM = "token";

    public Object loadUserInfo(CurrentUserInfo currentUserInfo, Class<?> returnType, WebRequest webRequest) {
        return loadUserInfo(currentUserInfo.required(), currentUserInfo.withDetails(), returnType, webRequest);
    }

    public Object loadUserInfo(boolean required, boolean withDetails, Class<?> returnType, WebRequest webRequest) {
        String appId = webRequest.getHeader(HttpHeader.APP_ID);
        String token = webRequest.getHeader(HttpHeader.TOKEN);
        String tokenParams = webRequest.getParameter(TOKEN_PARAM);

        if (StringUtils.isNoneBlank(tokenParams)) {
            token = tokenParams;
        }

        String userIdentityJson = webRequest.getHeader("X-User-Identity");
        String auth = webRequest.getHeader(HttpHeaders.AUTHORIZATION);
        String userInfoUrl = uaaUserInfoUrl;
        if (StringUtils.isBlank(token) && StringUtils.isBlank(auth) && StringUtils.isBlank(userIdentityJson)) {
            if (required) {
                log.debug("token is blank");
                throw new UnauthorizedException();
            } else {
                return null;
            }
        }
        HttpHeaders headers;
        if (StringUtils.isNoneBlank(userIdentityJson)) {
            return getUserInfoFromJson(appId, userIdentityJson, required, withDetails, returnType);
        } else if (StringUtils.isNoneBlank(token)) {
            headers = new HttpHeaders();
            headers.add(HttpHeader.APP_ID, appId);
            headers.add(HttpHeader.TOKEN, token);
        } else {
            headers = new HttpHeaders();
            headers.add(HttpHeader.APP_ID, appId);
            headers.add(HttpHeaders.AUTHORIZATION, auth);
        }
        return requestUserInfo(userInfoUrl, headers,
                required, withDetails,
                returnType);
    }

    public Object loadUserInfoByToken(String appId, String token, boolean required, boolean withDetails, Class<?> returnType) {
        Assert.notNull(token, "Token must be not null");

        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeader.TOKEN, token);
        headers.add(HttpHeader.APP_ID, appId);
        return requestUserInfo(uaaUserInfoUrl, headers, required, withDetails, returnType);
    }

    private Object getUserInfoFromJson(String appId, String userIdentityJson,
                                       boolean required, boolean withDetails, Class<?> returnType) {
        UserIdentity userIdentity;
        try {
            userIdentity = objectMapper.readValue(userIdentityJson, UserIdentity.class);
        } catch (IOException e) {
            if (required) {
                throw new UnauthorizedException();
            } else {
                return null;
            }
        }
        if (returnType == UserIdentity.class) {
            return userIdentity;
        } else {
            HttpHeaders headers = new HttpHeaders();
            headers.add("X-User-Id", userIdentity.getId());
            headers.add(HttpHeader.APP_ID, appId);
            return requestUserInfo(uaaUserInfoUrl + "/id", headers, required, withDetails, returnType);
        }
    }

    @SuppressWarnings("unchecked")
    private Object requestUserInfo(String uaaUserInfoUrl, HttpHeaders headers,
                                   boolean required, boolean withDetails,
                                   Class<?> returnType) {
        HttpEntity requestEntity = new HttpEntity(headers);
        String userInfoUrl = uaaUserInfoUrl + "?withResources=false";

        if (!withDetails) {
            userInfoUrl += "&withDetails=false";
        }
        try {
            ResponseEntity<InvokeResult> response = restTemplate.exchange(userInfoUrl, HttpMethod.GET,
                    requestEntity, InvokeResult.class);
            InvokeResult invokeResult = response.getBody();
            if (invokeResult != null && OK_STATUS.equals(invokeResult.getStatus())) {
                Map userInfoMap = (Map) invokeResult.getData();
                if (returnType == Map.class) {
                    return userInfoMap;
                }
                if (returnType == UserInfo.class && withDetails) {
                    Object details = userInfoMap.get(DETAILS_KEY);
                    userInfoMap.remove(DETAILS_KEY);
                    UserInfo<UserInfoDetail> userInfo = (UserInfo<UserInfoDetail>) BeanMapper.map(userInfoMap, UserInfo.class);
                    userInfo.setDetails(BeanMapper.map(details, UserInfoDetail.class));
                    return userInfo;
                }
                if (returnType.getSuperclass() == UserInfo.class && withDetails) {
                    Class detailsClazz = ClassUtil.getClassGenericType(returnType);
                    Object details = userInfoMap.get(DETAILS_KEY);
                    userInfoMap.remove(DETAILS_KEY);
                    UserInfo userInfo = (UserInfo) BeanMapper.map(userInfoMap, returnType);
                    userInfo.setDetails(BeanMapper.map(details, detailsClazz));
                    return userInfo;
                }
                return BeanMapper.map(userInfoMap, returnType);
            } else {
                if (required) {
                    throw new UnauthorizedException();
                } else {
                    return null;
                }
            }
        } catch (RestClientException restClientException) {
            if (required) {
                log.debug(restClientException.getMessage());
                throw new UnauthorizedException(restClientException.getMessage());
            } else {
                return null;
            }
        }
    }
}
